package com.net128.beatportapps.downloader;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Observable;

import org.apache.log4j.Logger;

public class DownloadWorker extends Observable implements Runnable {
	private final static Logger log=Logger.getLogger(DownloadWorker.class);
	private static final int MAX_BUFFER_SIZE = 8192;
	public static final String STATUSES[] = 
		{ "Waiting", "Downloading", "Paused", "Complete", "Cancelled", "Error" };
	public static final int WAITING = 0;
	public static final int DOWNLOADING = 1;
	public static final int PAUSED = 2;
	public static final int COMPLETE = 3;
	public static final int CANCELLED = 4;
	public static final int ERROR = 5;
	private URL url; // download URL
	private long size; // size of download in bytes
	private int downloaded; // number of bytes downloaded
	private int status; // current status of download
	private long time=0;
	private long started=0;
	private long stopped=0;
	private long paused=0;
	private float progress=0;
	private int rate=0;
	private String fileName;
	private String user;
	private String password;
	private String sessionCookies;
	private StatusChangedObserver statusChangedObserver;
	private static long totalDownloaded=0;
	private static long total=0;

	// Constructor for Download.
	public DownloadWorker(URL url, String fileName, String user, String password, 
			StatusChangedObserver statusChangedObserver, long size) {
		this.url = url;
		this.size = size;
		downloaded = 0;
		this.fileName=fileName;
		this.user=user;
		this.password=password;
		this.statusChangedObserver=statusChangedObserver;
		if(!new File(fileName).exists()) {
			status = WAITING;
			changeTotal(0, size);
		} else {
			status = COMPLETE;
			stateChanged();
		}
	}
	
	// Get this download's URL.
	public String getFile() {
		return fileName;
	}

	// Get this download's size.
	public long getSize() {
		return size;
	}

	// Get this downloaded bytes.
	public int getDownloaded() {
		return downloaded;
	}

	// Get this download's progress.
	public float getProgress() {
		return progress;
	}

	// Get this download's rate.
	public int getRate() {
		return rate;
	}

	public int getStatus() {
		return status;
	}

	public long getStarted() {
		return started;
	}

	public void resume() {
		if(status==ERROR) {
			return;
		}
		status = DOWNLOADING;
		if(status == PAUSED) {
			paused+=System.currentTimeMillis()-stopped;
		}
		stateChanged();
		download();
	}

	public void cancel() {
		status = CANCELLED;
		stateChanged();
	}

	private void error() {
		status = ERROR;
		stateChanged();
	}

	private void download() {
		Thread thread = new Thread(this);
		thread.start();
	}
	
	public static synchronized void initTotals() {
		total=0;
		totalDownloaded=0;
	}
	
	public static synchronized long getTotal() {
		return total;
	}
	
	public static synchronized long getToalDownloaded() {
		return totalDownloaded;
	}

	// Download file.
	public void run() {
		RandomAccessFile file = null;
		InputStream stream = null;

		try {
			// Open connection to URL.
			HttpURLConnection connection = (HttpURLConnection) url
					.openConnection();

			if(sessionCookies==null) {
				sessionCookies=new BeatportAccessor().getSessionCookies(user, password);
			}
			connection.setRequestProperty("Cookie",sessionCookies);
			
			// Specify what portion of file to download.
			if(downloaded==0) {
				connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
			}
			
			// Connect to server.
			connection.connect();

			// Make sure response code is in the 200 range.
			if (connection.getResponseCode() / 100 != 2) {
				log.error("Unexpected response from server: "+connection.getResponseCode());
				error();
				return;
			}

			if (connection.getContentType().indexOf("text")>=0) {
				log.error("Unexpected response type from server: "+connection.getContentType());
				error();
				return;
			}

			// Check for valid content length.
			int contentLength = connection.getContentLength();
			if (contentLength < 1) {
				log.error("Invalid content length: "+connection.getContentLength());
				error();
				return;
			}

			/*
			 * Set the size for this download if it hasn't been already set.
			 */
			if (size == -1) {
				size = contentLength;
				stateChanged();
			} else if(size!=contentLength) {
				changeTotal(0, contentLength-size);
			}

			// Open file and seek to the end of it.
			file = new RandomAccessFile(fileName, "rw");
			file.seek(downloaded);

			stream = connection.getInputStream();
			if(started==0) {
				started=System.currentTimeMillis();
				time=started;
			}
			statusChangedObserver.statusChanged(status);
			
			byte buffer[] = new byte[MAX_BUFFER_SIZE];
			while (status == DOWNLOADING) {
				if (size - downloaded < MAX_BUFFER_SIZE) {
					buffer = new byte[(int)(size - downloaded)];
				}

				// Read from server into buffer.
				int read = stream.read(buffer);
				if (read == -1)
					break;

				// Write buffer to file.
				file.write(buffer, 0, read);
				downloaded += read;
				changeTotal(read, 0);
				stateChanged();
			}

			/*
			 * Change status to complete if this point was reached because
			 * downloading has finished.
			 */
			if (status == DOWNLOADING) {
				stopped=System.currentTimeMillis();
				status = COMPLETE;
			}
			statusChangedObserver.statusChanged(status);
			stateChanged();
		} catch (Exception e) {
			e.printStackTrace();
			error();
		} finally {
			try {
				file.close();
			} catch (Exception e) {
			}

			try {
				stream.close();
			} catch (Exception e) {
			}
		}
	}

	private static synchronized void changeTotal(long dDownloaded, long dTotal) {
		total+=dTotal;
		totalDownloaded+=dDownloaded;
		//System.out.println(totalDownloaded+"/"+total);
	}
	
	private void stateChanged() {
		time=System.currentTimeMillis();
		progress=((float) downloaded / size) * 100;
		if(started>0) {
			rate=(int)(downloaded/1.024/(time-started-paused));
		}
		setChanged();
		notifyObservers();
		if(status!=COMPLETE) {
			statusChangedObserver.statusChanged(status);
		}
	}	
}

